﻿namespace LizardChess
open System
open System.IO
open System.Runtime.Serialization.Formatters.Binary
open System.Runtime.Serialization
module Varn =

    ///cur - create Current Varn
    let cur (nm,isw) = {name=nm;isw=isw;brchs=[]}
    let emp = {name="NotSet";isw=true;brchs=[]}
    
    ///findsv - finds the selvar given posn and set of posns
    let findsv (mb:posn) (curbs:posn list) =
        let mba = mb.Mhst |> List.rev |> List.toArray
        //function to find match
        let mtch psn =
            let curba = psn.Mhst |> List.rev |> List.toArray
            curba.Length >= mba.Length && curba.[0..mba.Length-1] = mba
        curbs |> List.tryFindIndex mtch
    ///findnmvs - finds the next moves given posn and set of posns
    let findnmvs (mb:posn) (curbs:posn list) =
        let mba = mb.Mhst |> List.rev |> List.toArray
        //function to find match with extra move
        let mtch psn =
            let curba = psn.Mhst |> List.rev |> List.toArray
            curba.Length > mba.Length && curba.[0..mba.Length-1] = mba
        curbs |> List.filter mtch |> List.map (fun curb -> (curb.Mhst|>List.rev).[mba.Length]) |> Set.ofList |> Set.toList
    ///mrgbrch - merges a new branch into a list of branches
    let mrgbrch (mb:posn) (curbs:posn list) =
        // either same as existing branch and then either do nothing or replace
        // or extra branch and need to put next to the nearest
        let mba = mb.Mhst |> List.rev |> List.toArray
        let mtchba = mba.[0..mba.Length-2]
        let mtchb = mtchba |> List.ofArray  
        //function to add to existing branch
        let rec addex front rear fnd =
            if rear = [] then front,fnd
            else
                let curb = (rear.Head).Mhst |> List.rev
                let curba = curb |> List.toArray
                if curb.Length < mtchb.Length && curba = mtchba.[0..curba.Length-1] then
                    (front @ mb::rear.Tail),true
                elif not (curb.Length < mtchb.Length) && curba.[0..mtchba.Length-1] = mtchba then
                    if mba.[mba.Length-1] = curba.[mba.Length-1] then
                        (front@rear),true
                    else
                        (front @ mb::rear.Tail),true
                else
                    addex (front@[rear.Head]) rear.Tail false
        //function to find length same for two move list
        let rec smmv m1 m2 no =
            if m1=[] || m2=[] || not (m1.Head=m2.Head) then no
            else smmv m1.Tail m2.Tail (no+1)
        //function to find index of best match
        let rec fndidx rear cidx no idx =
            if rear = [] then idx
            else
                let curb = List.rev(rear.Head.Mhst)
                let cno = smmv curb (List.rev mb.Mhst) 0
                let nidx = if cno < no then idx else cidx
                let nno = if cno < no then no else cno
                fndidx rear.Tail (cidx+1) nno nidx
        // use functions
        let ans,fnd = addex [] curbs false
        if fnd then ans
        else     
            //need to find nearest branch
            let nidx = fndidx curbs 0 0 0
            let curbsa = curbs |> List.toArray
            let frnt = curbsa.[0..nidx] |> List.ofArray
            let rear = if nidx<curbs.Length-1 then curbsa.[nidx+1..curbs.Length-1]|>List.ofArray else []
            frnt@[mb]@rear
            
    ///add - updates the variation with moves from a game move history
    let add cur (psn:posn) = 
        //don't add if wrong colour
        if not (Mov.Isw(psn.Mhst.Head))=cur.isw then cur
        elif cur.brchs=[] then {cur with brchs=[psn]}
        else 
            {cur with brchs=mrgbrch psn cur.brchs}
    ///del - deletes a line from the variation given index of branch to delete
    let del cur (sel:int) =
        let brs = cur.brchs
        let abrs = brs |> List.toArray
        let nbrs1 = if sel=0 then [] else (List.ofArray abrs.[0..sel-1])
        let nbrs2 = if sel=abrs.Length-1 then [] else (List.ofArray abrs.[sel+1..abrs.Length-1])
        {cur with brchs=nbrs1@nbrs2}
    ///mvl2lines - support function that converts a move list to lines
    let mvl2lines mvl =
        let rec genml ml (mh:move list) isw =
            if mh = [] then List.rev ml
            else
                if isw then genml ([|mh.Head.PGN;""|]::ml) mh.Tail (not isw)
                else genml ([|ml.Head.[0];mh.Head.PGN|]::ml.Tail) mh.Tail (not isw)
        genml [] mvl true
        |> List.toArray
    ///mvll2lines - support function that converts a move list list to lines
    let mvll2lines mvll =
        let linesl = mvll |> List.map mvl2lines 
        //find max lines length
        let maxl = ref 0
        linesl |> List.iter (fun lines -> if lines.Length> !maxl then maxl := lines.Length)
        //grow all lines to maxl
        let gline (l:string[][]) =
            let ans = Array.create !maxl [|"";""|]
            for i = 0 to l.Length-1 do
                ans.[i] <- l.[i]
            ans
        let glinesl = linesl |> List.map gline
        //merge two line in the list
        let mline = Array.map2 (Array.append) 
        List.reduce mline glinesl

    ///lines - gets an array of lines for display
    let lines cur = if cur.brchs=[] then [||] else mvll2lines (List.map (fun b -> List.rev(b.Mhst)) cur.brchs) 
    ///mvl - gets a move list given a cur and the column and the row
    let mvl (cur,cl,rwi) = 
        let fmvla = List.rev(cur.brchs.[cl].Mhst) |> List.toArray
        let rw = if rwi<fmvla.Length then rwi else fmvla.Length-1
        fmvla.[0..rw]|> List.ofArray 

    //STORAGE elements
    //set up paths
    let wfold = 
        let ans = Path.Combine(Ref.basefold,"White")
        Directory.CreateDirectory(ans)|>ignore
        ans
    let bfold = 
        let ans = Path.Combine(Ref.basefold,"Black")
        Directory.CreateDirectory(ans)|>ignore
        ans
    ///wvars gets list of white varns
    let wvars() = Directory.GetFiles(wfold,"*.txt") |> Array.map Path.GetFileNameWithoutExtension
    let wvarso() = wvars()|>Array.map (fun s -> s :> obj)
    ///bvars gets list of white varns
    let bvars() = Directory.GetFiles(bfold,"*.txt") |> Array.map Path.GetFileNameWithoutExtension
    let bvarso() = bvars()|>Array.map (fun s -> s :> obj)

    /// cur2txt - generates array of string of moves from cur varn
    let cur2txt cur =
        cur.brchs |> List.map Posn.psn2str |> List.toArray
    ///save - serializes the varn to a file in binary
    let save cur =
        try
            //set this to file in White folder with filename same as name
            let fn = Path.Combine((if cur.isw then wfold else bfold), cur.name + ".txt")
            File.WriteAllLines(fn,cur2txt cur) 
            "Save successful for variation: " + cur.name
        with
            |e -> "Save failed with error: " + e.ToString()
    ///save - saves the cur with a new name and returns the renamed cur
    let saveas (cur,nm) =
        let ans = {cur with name = nm}
        save ans |> ignore
        ans
    ///load - deserializes to a varn from a file in binary
    let load (nm,isw) =
        //set this to file in White folder with filename same as name
        let fn = Path.Combine((if isw then wfold else bfold), nm + ".txt")
        let txtlines = File.ReadAllLines(fn) |> List.ofArray
        let genb mvlft = 
            let psn = Posn.st()
            Posn.DoMovesft(mvlft,psn)
        let bs = txtlines |> List.map (fun ln -> ln.Trim().Split([|' '|]) |> Array.map Mov.fromuci |> List.ofArray) |> List.map genb
        {name=nm;isw=isw;brchs=bs}
    ///load - deserializes to a varn from a file or files 
    let loada (nm,isw) =
        let vnnames =
            if nm = "<All>" then 
                (if isw then wvars() else bvars())|> Array.toList
            else
                [nm]
        let brs = vnnames |> List.map (fun nm -> load(nm,isw).brchs)|>List.concat
        {name=nm;isw=isw;brchs=brs}


    ///load - deserializes to a varn from a file in binary
    let loadtxt (nm,isw) =
        //set this to file in White folder with filename same as name
        let fn = Path.Combine((if isw then wfold else bfold), nm + ".txt")
        let txtlines = File.ReadAllLines(fn) 
        txtlines
    ///delete - deletes the variation file 
    let delete (nm,isw) =
        //set this to file in White folder with filename same as name
        File.Delete(Path.Combine((if isw then wfold else bfold), nm + ".txt"))
    // psn2pgn - generates array of string of moves from cur varn
    let psn2pgn psn =
        let rec mvl2pgn str (mvl:move list) =
            if mvl=[] then str
            else mvl2pgn (str + " " + (mvl.Head.PGN)) mvl.Tail 
        let ans = psn.Mhst |> List.rev |> mvl2pgn "" 
        ans.Trim()